#include "app_type.h"
#include "interfaces.h"

#if IFS_I2C_EN

#include "M45x_I2C.h"
#include "M451Series.h"

#include "core.h"

#define M45X_I2C_NUM				2

#define I2C0_SDA_IO_CONFIG_REG			SYS->GPA_MFPL
#define I2C0_SDA_IO_CONFIG_MASK			SYS_GPA_MFPL_PA2MFP_Msk
#define I2C0_SDA_IO_CONFIG_VALUE		SYS_GPA_MFPL_PA2MFP_I2C0_SDA
#define I2C0_SCL_IO_CONFIG_REG			SYS->GPA_MFPL
#define I2C0_SCL_IO_CONFIG_MASK			SYS_GPA_MFPL_PA3MFP_Msk
#define I2C0_SCL_IO_CONFIG_VALUE		SYS_GPA_MFPL_PA3MFP_I2C0_SCL
#define I2C1_SDA_IO_CONFIG_REG			SYS->GPE_MFPL
#define I2C1_SDA_IO_CONFIG_MASK			SYS_GPE_MFPL_PE0MFP_Msk
#define I2C1_SDA_IO_CONFIG_VALUE		SYS_GPE_MFPL_PE0MFP_I2C1_SDA
#define I2C1_SCL_IO_CONFIG_REG			SYS->GPC_MFPL
#define I2C1_SCL_IO_CONFIG_MASK			SYS_GPC_MFPL_PC4MFP_Msk
#define I2C1_SCL_IO_CONFIG_VALUE		SYS_GPC_MFPL_PC4MFP_I2C1_SCL

#define I2C_CON_I2C_STS		I2C_CTL_SI_Msk
#define I2C_CON_START		I2C_CTL_STA_Msk
#define I2C_CON_STOP		I2C_CTL_STO_Msk
#define I2C_CON_ACK			I2C_CTL_AA_Msk

#define I2C_SET_CONTROL_REG(i2c, CTL_Msk) ( (i2c)->CTL = ((i2c)->CTL & ~0x3cul) | (CTL_Msk) )

static void *m45x_i2c_param[M45X_I2C_NUM];
static void (*m45x_i2c_callback[M45X_I2C_NUM])(void *, int32_t, int32_t);

struct i2c_ctrl_t
{
	uint16_t enable : 1;
	uint16_t rw : 1;	// current mode: read 1 , write 0
	uint16_t callback_enable : 1;
	uint16_t :13;
	uint16_t chip_addr;
	
	uint8_t *read;
	uint16_t read_len;
	uint16_t read_prt;
	uint8_t *write;
	uint16_t write_len;
	uint16_t write_prt;	
} static i2c_ctrl[M45X_I2C_NUM];

vsf_err_t m45x_i2c_init(uint8_t index)
{
#if __VSF_DEBUG__
	if (index >= M45X_I2C_NUM)
		return VSFERR_NOT_SUPPORT;
#endif

	if (index == 0)
	{
		I2C0_SDA_IO_CONFIG_REG &= ~I2C0_SDA_IO_CONFIG_MASK;
		I2C0_SDA_IO_CONFIG_REG |= I2C0_SDA_IO_CONFIG_VALUE;
		I2C0_SCL_IO_CONFIG_REG &= ~I2C0_SCL_IO_CONFIG_MASK;
		I2C0_SCL_IO_CONFIG_REG |= I2C0_SCL_IO_CONFIG_VALUE;
		CLK->APBCLK0 |= CLK_APBCLK0_I2C0CKEN_Msk;
	}
	else if (index == 1)
	{
		I2C1_SDA_IO_CONFIG_REG &= ~I2C1_SDA_IO_CONFIG_MASK;
		I2C1_SDA_IO_CONFIG_REG |= I2C1_SDA_IO_CONFIG_VALUE;
		I2C1_SCL_IO_CONFIG_REG &= ~I2C1_SCL_IO_CONFIG_MASK;
		I2C1_SCL_IO_CONFIG_REG |= I2C1_SCL_IO_CONFIG_VALUE;
		CLK->APBCLK0 |= CLK_APBCLK0_I2C1CKEN_Msk;
	}
	return VSFERR_NONE;
}

vsf_err_t m45x_i2c_fini(uint8_t index)
{
#if __VSF_DEBUG__
	if (index >= M45X_I2C_NUM)
		return VSFERR_NOT_SUPPORT;
#endif

	if (index == 0)
	{
		I2C0->CTL = 0;
		I2C0_SDA_IO_CONFIG_REG &= ~I2C0_SDA_IO_CONFIG_MASK;
		I2C0_SCL_IO_CONFIG_REG &= ~I2C0_SCL_IO_CONFIG_MASK;
		CLK->APBCLK0 &= ~CLK_APBCLK0_I2C0CKEN_Msk;
	}
	else if (index == 1)
	{
		I2C1->CTL = 0;
		I2C1_SDA_IO_CONFIG_REG &= ~I2C1_SDA_IO_CONFIG_MASK;
		I2C1_SCL_IO_CONFIG_REG &= ~I2C1_SCL_IO_CONFIG_MASK;
		CLK->APBCLK0 &= ~CLK_APBCLK0_I2C1CKEN_Msk;
	}
	return VSFERR_NONE;
}

vsf_err_t m45x_i2c_config(uint8_t index, uint16_t kHz, void *param,
							void (*callback)(void *, int32_t, int32_t))
{
	vsf_err_t err;
	I2C_T *i2c;
	struct m45x_info_t *info;
	uint32_t div;

#if __VSF_DEBUG__
	if (index >= M45X_I2C_NUM)
		return VSFERR_NOT_SUPPORT;
#endif

	if (index == 0)
	{
		i2c = I2C0;
	}
	else if (index == 1)
	{
		i2c = I2C1;
	}
	
	err = m45x_interface_get_info(&info);
	if (err != VSFERR_NONE)
	{
		return VSFERR_FAIL;
	}
	
	m45x_i2c_param[index] = param;
	m45x_i2c_callback[index] = callback;

	div = info->pclk_freq_hz / (kHz * 1000 * 4) - 1;
	if (div < 4)
		div = 4;
	else if (div > 255)
		div = 255;
	i2c->CLKDIV = div;
	i2c->CTL = I2C_CTL_INTEN_Msk | I2C_CTL_I2CEN_Msk;
	
	if (index == 0)
	{
		NVIC_EnableIRQ(I2C0_IRQn);
	}
	else if (index == 1)
	{
		NVIC_EnableIRQ(I2C1_IRQn);
	}

	return VSFERR_NONE;
}

vsf_err_t m45x_i2c_xfer_nb(uint8_t index, uint16_t chip_addr,
							uint8_t read_first,
							uint8_t *read, uint16_t read_len,
							uint8_t *write, uint16_t write_len)
{
#if __VSF_DEBUG__
	if (index >= M45X_I2C_NUM)
	{
		return VSFERR_NOT_SUPPORT;
	}
#endif
	
	i2c_ctrl[index].chip_addr = chip_addr;
	
	i2c_ctrl[index].read = read;
	i2c_ctrl[index].read_len = read_len;
	i2c_ctrl[index].read_prt = 0;
	i2c_ctrl[index].write = write;
	i2c_ctrl[index].write_len = write_len;
	i2c_ctrl[index].write_prt = 0;
	
	if (read_first && read)
	{
		i2c_ctrl[index].rw = 1;
	}
	else if ((!read_first) && write)
	{
		i2c_ctrl[index].rw = 0;
	}
	else
	{
		return VSFERR_FAIL;
	}
	
	
	i2c_ctrl[index].enable = 1;
	i2c_ctrl[index].callback_enable = 1;
	if (index == 0)
	{
		I2C0->TOCTL = I2C_TOCTL_TOIF_Msk;
		I2C0->TOCTL = I2C_TOCTL_TOCEN_Msk;
		I2C_SET_CONTROL_REG(I2C0, I2C_CON_START);
	}
	else if (index == 1)
	{
		I2C1->TOCTL = I2C_TOCTL_TOIF_Msk;
		I2C1->TOCTL = I2C_TOCTL_TOCEN_Msk;
		I2C_SET_CONTROL_REG(I2C1, I2C_CON_START);
	}

	return VSFERR_NONE;
}

void I2C0_IRQHandler(void)
{
	if (i2c_ctrl[0].enable == 0)
		goto error;	
	
	if (I2C0->TOCTL & I2C_TOCTL_TOIF_Msk)
	{
		goto error;
	}
	else if (I2C0->CTL & I2C_CON_I2C_STS)
	{
		uint32_t status = I2C0->STATUS;

		if (i2c_ctrl[0].rw) // read phase
		{
			if ((status == 0x08) || (status == 0x10))
			{
				I2C0->DAT = (i2c_ctrl[0].chip_addr << 1) + 1;
				I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS);
			}
			else if (status == 0x40)
			{
				if (i2c_ctrl[0].read_len > 1)
				{
					// host reply ack
					I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS | I2C_CON_ACK);
				}
				else if (i2c_ctrl[0].read_len == 1)
				{
					// host reply nack
					I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS);
				}
				else
				{
					goto error;
				}
			}
			else if (status == 0x50)
			{
				if (i2c_ctrl[0].read_prt < i2c_ctrl[0].read_len)
					i2c_ctrl[0].read[i2c_ctrl[0].read_prt++] = I2C0->DAT;
				if (i2c_ctrl[0].read_prt < i2c_ctrl[0].read_len - 1)
				{
					// host reply ack
					I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS | I2C_CON_ACK);
				}
				else
				{
					// host reply nack
					I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS);
				}
			}
			else if (status == 0x58)
			{
				if (i2c_ctrl[0].read_prt < i2c_ctrl[0].read_len)
					i2c_ctrl[0].read[i2c_ctrl[0].read_prt++] = I2C0->DAT;
				
				i2c_ctrl[0].read = NULL;
				
				if (i2c_ctrl[0].write != NULL)
				{
					i2c_ctrl[0].rw = 0;
					I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS | I2C_CON_START);
				}
				else
				{
					I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS | I2C_CON_STOP);
					I2C0->TOCTL = 0;
					if (m45x_i2c_callback[0] != NULL &&
						i2c_ctrl[0].callback_enable)
					{
						i2c_ctrl[0].enable = 0;
						i2c_ctrl[0].callback_enable = 0;
						m45x_i2c_callback[0](m45x_i2c_param[0],
												i2c_ctrl[0].read_prt,
												i2c_ctrl[0].write_prt);
					}
				}
			}
			else
			{
				goto error;
			}	
		}
		else // write phase
		{
			if ((status == 0x08) || (status == 0x10))	// start send finish
			{
				I2C0->DAT = i2c_ctrl[0].chip_addr << 1;
				I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS);
			}
			else if ((status == 0x18) || (status == 0x28))	// addr/data send finish and ACK received
			{
				if (i2c_ctrl[0].write_prt < i2c_ctrl[0].write_len)
				{
					I2C0->DAT = i2c_ctrl[0].write[i2c_ctrl[0].write_prt++];
					I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS);
				}
				else
				{
					i2c_ctrl[0].write = NULL;
					if (i2c_ctrl[0].read != NULL)
					{
						i2c_ctrl[0].rw = 1;
						I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS | I2C_CON_START);
					}
					else
					{
						I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS | I2C_CON_STOP);
						I2C0->TOCTL = 0;
						if (m45x_i2c_callback[0] != NULL &&
							i2c_ctrl[0].callback_enable)
						{
							i2c_ctrl[0].enable = 0;
							i2c_ctrl[0].callback_enable = 0;
							m45x_i2c_callback[0](m45x_i2c_param[0],
													i2c_ctrl[0].read_prt,
													i2c_ctrl[0].write_prt);
						}
					}
				}
			}
			else
			{
				goto error;
			}			
		}
	}
	return;

error:	
	I2C0->TOCTL = I2C_TOCTL_TOIF_Msk;		
	I2C_SET_CONTROL_REG(I2C0, I2C_CON_I2C_STS | I2C_CON_STOP);
	
	i2c_ctrl[0].enable = 0;	
	if (m45x_i2c_callback[0] != NULL && i2c_ctrl[0].callback_enable)
	{
		i2c_ctrl[0].callback_enable = 0;
		m45x_i2c_callback[0](m45x_i2c_param[0], -1, -1);
	}
}

void I2C1_IRQHandler(void)
{
	if (i2c_ctrl[1].enable == 0)
		goto error;	
	
	if (I2C1->TOCTL & I2C_TOCTL_TOIF_Msk)
	{
		goto error;
	}
	else if (I2C1->CTL & I2C_CON_I2C_STS)
	{
		uint32_t status = I2C1->STATUS;

		if (i2c_ctrl[1].rw) // read phase
		{
			if ((status == 0x08) || (status == 0x10))
			{
				I2C1->DAT = (i2c_ctrl[1].chip_addr << 1) + 1;
				I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS);
			}
			else if (status == 0x40)
			{
				if (i2c_ctrl[1].read_len > 1)
				{
					// host reply ack
					I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS | I2C_CON_ACK);
				}
				else if (i2c_ctrl[1].read_len == 1)
				{
					// host reply nack
					I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS);
				}
				else
				{
					goto error;
				}
			}
			else if (status == 0x50)
			{
				if (i2c_ctrl[1].read_prt < i2c_ctrl[1].read_len)
					i2c_ctrl[1].read[i2c_ctrl[1].read_prt++] = I2C1->DAT;
				if (i2c_ctrl[1].read_prt < i2c_ctrl[1].read_len - 1)
				{
					// host reply ack
					I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS | I2C_CON_ACK);
				}
				else
				{
					// host reply nack
					I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS);
				}
			}
			else if (status == 0x58)
			{
				if (i2c_ctrl[1].read_prt < i2c_ctrl[1].read_len)
					i2c_ctrl[1].read[i2c_ctrl[1].read_prt++] = I2C1->DAT;
				
				i2c_ctrl[1].read = NULL;
				
				if (i2c_ctrl[1].write != NULL)
				{
					i2c_ctrl[1].rw = 0;
					I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS | I2C_CON_START);
				}
				else
				{
					I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS | I2C_CON_STOP);
					I2C1->TOCTL = 0;
					if (m45x_i2c_callback[1] != NULL &&
						i2c_ctrl[1].callback_enable)
					{
						i2c_ctrl[1].enable = 0;
						i2c_ctrl[1].callback_enable = 0;
						m45x_i2c_callback[1](m45x_i2c_param[1],
												i2c_ctrl[1].read_prt,
												i2c_ctrl[1].write_prt);
					}
				}
			}
			else
			{
				goto error;
			}	
		}
		else // write phase
		{
			if ((status == 0x08) || (status == 0x10))	// start send finish
			{
				I2C1->DAT = i2c_ctrl[1].chip_addr << 1;
				I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS);
			}
			else if ((status == 0x18) || (status == 0x28))	// addr/data send finish and ACK received
			{
				if (i2c_ctrl[1].write_prt < i2c_ctrl[1].write_len)
				{
					I2C1->DAT = i2c_ctrl[1].write[i2c_ctrl[1].write_prt++];
					I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS);
				}
				else
				{
					i2c_ctrl[1].write = NULL;
					if (i2c_ctrl[1].read != NULL)
					{
						i2c_ctrl[1].rw = 1;
						I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS | I2C_CON_START);
					}
					else
					{
						I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS | I2C_CON_STOP);
						I2C1->TOCTL = 0;
						if (m45x_i2c_callback[1] != NULL &&
							i2c_ctrl[1].callback_enable)
						{
							i2c_ctrl[1].enable = 0;
							i2c_ctrl[1].callback_enable = 0;
							m45x_i2c_callback[1](m45x_i2c_param[1],
													i2c_ctrl[1].read_prt,
													i2c_ctrl[1].write_prt);
						}
					}
				}
			}
			else
			{
				goto error;
			}			
		}
	}
	return;

error:
	I2C1->TOCTL = I2C_TOCTL_TOIF_Msk;		
	I2C_SET_CONTROL_REG(I2C1, I2C_CON_I2C_STS | I2C_CON_STOP);
	
	i2c_ctrl[1].enable = 0;
	if (m45x_i2c_callback[1] != NULL && i2c_ctrl[1].callback_enable)
	{
		i2c_ctrl[1].callback_enable = 0;
		m45x_i2c_callback[1](m45x_i2c_param[1], -1, -1);
	}
}

#endif

